docs(skills): objectstack-query factual sweep — 17 false behavioral claims corrected, -33 lines - #13740
Conversation
…laims corrected Flight (3) of the published-skills factual sweep. Every behavioral claim in skills/objectstack-query/** verified against the implementation with executed probes; corrections are byte-shrinking against the pinned token ratchets. Net: -33 lines, -255 tokens across the package. No ceiling raised. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EnE7G31tqbxN1rqpQmzurT
Contract review — PASS at head
|
Fixes #13717
Part of #13658
Flight ③ of the published-skills factual sweep: every behavioral claim in
skills/objectstack-query/**verified against the implementation, with executedprobes wherever a claim is behavior-bearing. Method per #13658 comments
5474435934 / 5474464789 / 5475032239 (content-class targeting first, both error
directions, cross-file contradiction scan first).
Governed surface — draft, human merge only. No ratchet ceiling raised.
Budget
Every ceiling is shrink-only and every file came in under it.
references/_index.mdwas verified (all nine schema paths resolve at
origin/main) and needed no edit.Corrections — 落点 | before | after
17 FALSE claims corrected. 11 of the 17 are the deader-than-real direction — the
docs marked a shipped capability dead or unreachable. That matches flight ②'s
majority finding.
SKILL.mdField References$field"schema-reserved — NOT executed… binds as a literal value, so the query silently returns zero rows"driver-sqlpushes it down. Comparison position only —$in/$ninmember and$betweenendpoint refused at parserules/filters.mdField ReferencesSKILL.mdaggregation driver-support calloutcount_distinct"; "stick to the first five"COUNT(DISTINCT x)on every SQL face; declared-but-uncompiled set is emptyrules/aggregation.mdsame calloutUnsupported aggregate functionSKILL.mdaggregation calloutdistinct: true"is also ignored there"rules/aggregation.mdsameSKILL.mdFiltered Aggregationfilter"schema-reserved… returns the unfiltered number — silently wrong results"INVALID_FILTER/400rules/aggregation.mdFiltered Aggregationactive_count would equal total!SKILL.mdDashboard Aggregation Patternfilterrules/aggregation.mdDISTINCT Aggregationdistinct: trueflag presented as a working alternativecount_distinctruns everywhere; the non-parsing example deletedrules/pagination.mdDISTINCT Queriesdistinct"schema-reserved… the query returns duplicate rows as if the flag were absent"QueryBuilder.distinct()goneSKILL.mdFull-Text Search prose$orof$containspredicates… Matching is case-insensitive"$icontains—$containsis contractually case-SENSITIVE, so the old text was self-contradictorySKILL.mdtwo asserted expansion outputs{ title: { $contains: … } }; single-term wrapped in$and$icontains; single term is a bare$or, no$andwrapperSKILL.mdformula-mirror paragraphsearchableFieldsentry, and the ingress gate has a dedicated refusal branch for itSKILL.mdCRM blueprint, KPI rowcompareTo: 'previousPeriod' | 'previousYear'(bare string)compareTo: { kind: … }— the bare string was removed in 17 with no acceptance windowSKILL.mdCRM blueprint, time-series rowcategoryGranularitydateGranularityon the widget's dataset selectionSKILL.mdCRM blueprint, matrix rowgroupingsDown+groupingsAcrossrows+columnsPlus derived-claim repairs in
evals/README.md(items 4, 5, 6, 7) whose rubricsencoded the same falsehoods — item 6 told the model to fail an answer using
having, which is enforced.One further correction, a framing rather than a surface claim:
rules/filters.mdlabelled
{ deleted_at: null }a ❌ Wrong null check. Measured, a barenullcomparand lowers to
IS NULLon both the in-memory evaluator and the SQL driverand returns exactly the same rows as
$null: true. The recommendation to prefer$nullis kept (it is the only spelling ofIS NOT NULL); the false "wrong"label is not.
Cross-file contradiction scan (run first, per the method)
Four contradictions inside the package; every one settled against the
implementation, not against the other document:
having— SKILL.md andrules/aggregation.mdsay ✅ Enforced;evals/README.mdsaid "schema-reserved, silently dropped". Implementation: enforced.
compareTo— SKILL.md said bare string,rules/aggregation.mdsays{ kind, dimension? }. Implementation: the object form; the string is retired.windowFunctions— SKILL.md andrules/aggregation.mdsay REMOVED/tombstoned;evals/README.mdsaid "schema-reserved". Implementation: removed.filter— three files agreed it was inert; all three were wrong.(Agreement between documents is not evidence — this is the case the method's
"never verify one document against another" rule exists for.)
Evidence — executed probes
Non-vacuity control satisfied several times over; every probe below ran a control
that discriminates.
$fieldcross-field. In-memory evaluator over three rows (over200/100,under50/100,equal100/100):$gt→["over"],$eq→["equal"],$lt→["under"]. Control, a plain literal$gt: 150on the same harness →["over"]. ThroughSqlDriveron better-sqlite3, same fixture pushed down:$gt { $field: 'budget' }→["over"]. The claim under test predicted zero rowson both paths.
count_distincton SQL.SqlDriver.aggregatewith{ function: 'count_distinct', field: 'dept', alias: 'n' }→[{"n":2}].Control, an undeclared function
medianon the same driver → threwINVALID_QUERY/400, so a refusal would have been visible.filter.engine.aggregate('deal', …)over 4 rows(2
closed_won):[{"total_deals":4,"won_deals":2}]. The claim under testpredicted
won_deals: 4.having. 4 orders across 3 customers,having: { order_count: { $gt: 1 } }→
[{"customer_id":"c1","order_count":2}]. Also confirmsgroupByauto-selectsthe grouped column without listing it in
fields.expandSearchToFilter('machine learning', …)→{"$and":[{"$or":[{"title":{"$icontains":"machine"}},{"content":{"$icontains":"machine"}}]},{"$or":[…"learning"…]}]};single term
'apollo'→ a bare{"$or":[…]}.Apollo/apollo:$contains: 'apollo'→["apollo"];$icontains: 'apollo'→["Apollo","apollo"].QuerySchema.safeParseREJECTScursor,joins,windowFunctions,distinct,aggregations[].distinct,array_agg,string_agg, and an aggregation with noalias; ACCEPTS all six aggregationfunctions,
top,having,aggregations[].filter, the fifteen documented filteroperators, and a
$fieldcomparand.DashboardWidgetSchemaREJECTScompareTo: 'previousPeriod'andcategoryGranularity, ACCEPTScompareTo: { kind: 'previousPeriod' };ReportSchemaREJECTSgroupingsDown/groupingsAcross, ACCEPTSdataset+rows+columns+values.isDateMacroTokenLIVE fortoday,yesterday,tomorrow,now,current_month_start,last_quarter_end,next_year_start,month_start,30_days_ago,2_weeks_from_now,current_year_start,current_year_end;isContextTokenLIVE forcurrent_user_id,current_org_id; both DEAD for the two near-misses the docnames as near-misses (
current_user,this_quarter_start). Parameterised formslive for all six declared units in both directions.
$exists/$null(prior art of docs(skills):$existsmeans HAS A VALUE — retire the MongoDB$existsclaim from objectstack-query #13577, re-verified atorigin/main).Rows valued / null / key-absent:
$exists: true→["v"];$null: true→["n","absent"]. HAS A VALUE, confirmed; both rows correct as merged.{ d: null }→["n","absent"]in memory and["n"]onSQL — identical to
{ d: { $null: true } }on each path.$null: false→["v"].Verified without execution, by enumerating the implementation:
MAX_EXPAND_DEPTH = 3;resolveFilterTokensreached from exactlyfind/findOne/count/aggregate/update/delete;SqlDriver.findWithWindowFunctions()exists and its builderemits argument-less
FUNC()(its own docblock says the skills' aggregation rulessay the same); the six search knobs each carry an
[EXPERIMENTAL — not enforced].describe()marker; the$searchFieldsdotted-path refusal text quoted inSKILL.md matches the ingress gate word for word; the flow-filter rule really is
severity: inFilter ? 'error' : 'warning';resolveDateMacros/resolveContextTokensexist in objectui
@object-ui/core; all four Skill Boundaries targets exist;compatibility: @objectstack/spec 17.x (Zod v4)matches 17.2.0 on zod ^4.4.3.Gates
Derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackin the worktree, on a NON-stale tree (the branch was merged up to
origin/main0f911eb9first — the first derivation warned thatlint.ymlandcross-package-test-inputs.mjs, which are themselves families in the list, werestale). 13 families, all run at
d601906b, the final commit:node scripts/check-ci-filter-parity.mjsnode scripts/check-cross-package-test-inputs.mjsnode scripts/check-shard-attestation.mjsnode scripts/check-skills-token-ratchet.mjsnode scripts/check-test-completeness.mjspnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:agent-test-spellingpnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:pm-governed-mergespnpm check:role-wordpnpm check:skill-compatibilitypnpm check:skill-frame-syncExit codes captured with
cmd > log 2>&1; EXIT=$?— before any pipe.check-test-completenessexit 3 is the script's ownPREREQUISITE NOT METbranch:it grades a saved
turbo run testlog, and the derived family names it with noargument. Its output says so verbatim and instructs the local reader to record it
as NOT MEASURED. Not a finding.
check:skill-examples— named in the dispatch brief, not in the derived familylist, and the derivation is right: that gate type-checks only fences marked with an
os:checkHTML-comment marker, andskills/objectstack-query/**carries zerosuch markers (control:
objectstack-ui,objectstack-i18nandobjectstack-formulaeach carry them, so the search discriminates). Its population here is empty. Two
bounded foreground attempts to run it anyway were blocked by its
@objectstack/client-reactbuild prerequisite, whose closure fails on pre-existingtype-only import gaps in
@objectstack/runtimeand@objectstack/verify(
Cannot find module '@objectstack/rest') — unrelated to this diff, and bothattempts hit the container's foreground cap. CI runs the gate on every PR regardless.
No changeset
Pure
skills/**diff, releasing nothing. Convention verified againstorigin/mainrather than assumed: the last six commits touching
skills/**— the threeobjectstack-data sweep PRs, the objectstack-formula sweep, the
$existscorrection,and the pm-dispatch rules PR — carry zero
.changeset/files between them. Theskip-changesetlabel is applied to this PR.NOT MEASURABLE (recorded, never annotated into the files)
Nine claims could not be settled from a package-scoped flight; none was corrected
and none is known false. They are the cross-package-runtime class flight ① named:
$inqueries "not N+1" — theQueryAST.expand.describe()asserts it andMAX_EXPAND_DEPTH = 3is read directly, but provingthe batching (rather than reading the claim) needs a driver call-count harness
over a relational fixture.
fields+whereonly, ignoring per-parentlimit/offset/orderBy— same fixture gap.$icontainsASCII-folding domain, and the__searchpinyin companion columnbehind
OS_SEARCH_PINYIN_ENABLED— needs a provisioned companion column.searchfieldsnarrowing answering400 INVALID_FIELDover the REST/protocolingress — the refusal text was matched in
metadata-protocol, but the HTTPstatus pairing needs a live server.
select/statuslabel→value mapping in search — the unit suite pins{ industry: { $in: ['retail'] } }; not re-run here against a real object.— the alias-renames-the-projection half was measured
(
[{"quarter":"2025-Q1","revenue":30},{"quarter":"2025-Q2","revenue":5}]), thetwo-tier agreement across a real
DATE_TRUNCdialect was not.DateGranularity's docblock; notexecuted across a week boundary.
os validate/os build/npm run validatefailing a dashboard widget whosedataset/dimensions/valuesdo not resolve (ADR-0021) — needs a scaffoldedproject.
— true of relational engines generally; not benchmarked here, and not the kind of
claim this sweep's oracle can settle.
Out-of-scope finding
packages/objectql/src/engine.tscarries a stale docblock describing the ADR-0061search expansion as "a server-resolved cross-field
$orof$contains" — the samesentence this PR corrects in the published skill. The implementation next door
(
search-filter.ts) emits$icontainsand documents the correction explicitly. Aninternal comment, outside this PR's file surface; reported for filing rather than
fixed here.
Generated by Claude Code
Generated by Claude Code